Skip to content

Automated Test: date-algorithm-enhanced #366

Closed

Conversation

admin-coderabbit
Copy link
Owner

@admin-coderabbit admin-coderabbit commented Feb 4, 2026

This pull request was automatically created by @coderabbitai/e2e-reviewer.

Batch created pull request.

Summary by CodeRabbit

  • Tests

    • Added test coverage to verify consistent UTC results when applying date overrides across different timezones.
  • Bug Fixes

    • Improved timezone-aware offset calculation for date overrides ensuring accurate time handling.
    • Enhanced date-specific overrides and working hours support in availability calculations with proper timezone alignment throughout the scheduling logic.

* fix date override for fixed round robin + time zone in date override

* check if slot is within working hours of fixed hosts

* add test for date override in different time zone

* fix date overrides for not fixed hosts (round robin)

* code clean up

* fix added test

* use the correct timezone of user for date overrides

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
@coderabbit-eval
Copy link

coderabbit-eval bot commented Feb 4, 2026

📝 Walkthrough

Walkthrough

This change introduces timezone-aware handling for date overrides and working hours in the scheduling system. The override computation logic is updated to use timezone-aware offset calculations, and availability checking now incorporates date-specific overrides and working hours with proper timezone alignment. A new optional timeZone property is added to the TimeRange type to support this behavior.

Changes

Cohort / File(s) Summary
Type Definitions
packages/types/schedule.d.ts
Added optional timeZone property to the TimeRange type to enable timezone tracking in time range data structures.
Override and Slot Processing
packages/lib/slots.ts, packages/trpc/server/routers/viewer/slots.ts
Replaced UTC hour/minute computation with timezone-aware offset logic for overrides. Enhanced availability checking to validate slots against date-specific overrides and working hours using organizer and invitee timezones. Added organizerTimeZone propagation throughout scheduling logic.
Schedule Tests
apps/web/test/lib/getSchedule.test.ts
Added test case verifying that UTC timeslots remain consistent when using different timezones for API calls in the presence of date overrides.

Sequence Diagram

sequenceDiagram
    actor Client
    participant API as API (slots.ts)
    participant Processor as Override Processor
    participant Validator as Availability Validator
    participant Store as Schedule Store

    Client->>API: Request schedule with dateOverrides,<br/>workingHours, timezone
    API->>Processor: Process overrides with timezone context
    Processor->>Processor: Compute organizerUtcOffset<br/>& inviteeUtcOffset
    Processor->>Processor: Calculate offset difference<br/>& adjust startTime/endTime
    Processor->>Validator: Pass adjusted overrides<br/>& working hours
    Validator->>Validator: For each slot:<br/>Check if within override window<br/>or working hours
    Validator->>Validator: Mark unavailable slots<br/>outside boundaries
    Validator->>Store: Return validated<br/>availability slots
    Store->>Client: Return timezone-aware<br/>schedule
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Timezones bent to our will at last,
Offsets calculated, shadows cast,
Overrides bloom in UTC's embrace,
Each slot knows its rightful place!

🚥 Pre-merge checks | ✅ 1 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is vague and generic, using non-descriptive terms like 'Automated Test' and 'date-algorithm-enhanced' that don't convey the actual scope of changes, which include timezone-aware scheduling logic across multiple files, not just a test. Replace with a more descriptive title that captures the main change, such as 'Add timezone-aware date overrides and working hours to scheduling logic' or 'Support timezone-aware date overrides in availability calculations'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch date-algorithm-enhanced

Comment @coderabbit eval help to get the list of available commands and usage tips.

@coderabbit-eval coderabbit-eval bot closed this Feb 4, 2026
Copy link

@coderabbit-eval coderabbit-eval bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@packages/trpc/server/routers/viewer/slots.ts`:
- Around line 114-116: The comparison using === between two Dayjs objects
(dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset,
"minutes")) is wrong; replace it with Dayjs value equality using .isSame(), e.g.
call .isSame(...) on one of the adjusted Dayjs instances (optionally with a
granularity like "minute") so the condition correctly detects identical moments;
update the expression where this check occurs to use .isSame instead of ===.
- Around line 141-145: The working-hours check incorrectly computes both start
and end from slotStartTime; update the end calculation to use the slot's end
time (e.g., slotEndTime.hour() * 60 + slotEndTime.minute() or
slotStartTime.plus(duration) if slotEndTime variable is named differently) so
that the start/end values accurately reflect the slot span, then keep the
existing boundary check against workingHour.startTime and workingHour.endTime
using the corrected end variable; adjust any variable names in the block
containing start, end, slotStartTime and workingHour accordingly.
- Around line 138-151: The callback passed to workingHours.find currently only
returns true when a violation is detected and otherwise returns undefined;
update the callback for workingHours.find (the anonymous arrow function
referencing workingHour and slotStartTime) to always return a boolean — return
true when the slot is outside the workingHour bounds and explicitly return false
otherwise (and also ensure you compute start/end from the correct slot time
variables if needed) so find() behaves predictably.
- Around line 106-131: The find() callback for dateOverrides may return
undefined on some paths; change the logic so the predicate always returns a
boolean and use a clearer API (e.g., replace dateOverrides.find(...) with
dateOverrides.some(...)) and return explicit true/false from the callback.
Locate the block referencing dateOverrides.find, organizerTimeZone,
slotStartTime, slotEndTime and dateOverrideExist; set dateOverrideExist when a
matching date is found and have the callback return true for both "slot outside
override" and "slot equals/overlaps" cases and false otherwise (or use some() to
directly assign dateOverrideExist = dateOverrides.some(...)). Ensure no path
leaves the callback without an explicit boolean return.

Comment on lines +106 to +131
dateOverrides.find((date) => {
const utcOffset = organizerTimeZone ? dayjs.tz(date.start, organizerTimeZone).utcOffset() * -1 : 0;

if (
dayjs(date.start).add(utcOffset, "minutes").format("YYYY MM DD") ===
slotStartTime.format("YYYY MM DD")
) {
dateOverrideExist = true;
if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) {
return true;
}
if (
slotEndTime.isBefore(dayjs(date.start).add(utcOffset, "minutes")) ||
slotEndTime.isSame(dayjs(date.start).add(utcOffset, "minutes"))
) {
return true;
}
if (slotStartTime.isAfter(dayjs(date.end).add(utcOffset, "minutes"))) {
return true;
}
}
})
) {
// slot is not within the date override
return false;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Refactor: find() callback should always return a boolean value.

Static analysis correctly flags that this callback doesn't return a value on all paths. When the date matches but the slot is within the override bounds, undefined is implicitly returned. While this works due to JavaScript's truthy/falsy handling, it's error-prone and hard to reason about.

Consider refactoring to use some() with explicit returns, or restructure the logic:

♻️ Suggested refactor for clarity
-  if (
-    dateOverrides.find((date) => {
-      const utcOffset = organizerTimeZone ? dayjs.tz(date.start, organizerTimeZone).utcOffset() * -1 : 0;
-
-      if (
-        dayjs(date.start).add(utcOffset, "minutes").format("YYYY MM DD") ===
-        slotStartTime.format("YYYY MM DD")
-      ) {
-        dateOverrideExist = true;
-        if (dayjs(date.start).add(utcOffset, "minutes").isSame(dayjs(date.end).add(utcOffset, "minutes"))) {
-          return true;
-        }
-        if (
-          slotEndTime.isBefore(dayjs(date.start).add(utcOffset, "minutes")) ||
-          slotEndTime.isSame(dayjs(date.start).add(utcOffset, "minutes"))
-        ) {
-          return true;
-        }
-        if (slotStartTime.isAfter(dayjs(date.end).add(utcOffset, "minutes"))) {
-          return true;
-        }
-      }
-    })
-  ) {
+  const slotOutsideOverride = dateOverrides.some((date) => {
+    const utcOffset = organizerTimeZone ? dayjs.tz(date.start, organizerTimeZone).utcOffset() * -1 : 0;
+    const overrideStart = dayjs(date.start).add(utcOffset, "minutes");
+    const overrideEnd = dayjs(date.end).add(utcOffset, "minutes");
+
+    if (overrideStart.format("YYYY MM DD") !== slotStartTime.format("YYYY MM DD")) {
+      return false; // Not the same day
+    }
+
+    dateOverrideExist = true;
+
+    // Check if slot is outside the override window
+    if (overrideStart.isSame(overrideEnd)) {
+      return true; // Zero-length override blocks the day
+    }
+    if (slotEndTime.isBefore(overrideStart) || slotEndTime.isSame(overrideStart)) {
+      return true;
+    }
+    if (slotStartTime.isAfter(overrideEnd)) {
+      return true;
+    }
+    return false; // Slot is within override
+  });
+
+  if (slotOutsideOverride) {
     // slot is not within the date override
     return false;
   }
🧰 Tools
🪛 Biome (2.3.13)

[error] 106-106: This callback passed to find() iterable method should always return a value.

Add missing return statements so that this callback returns a value on all execution paths.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
In `@packages/trpc/server/routers/viewer/slots.ts` around lines 106 - 131, The
find() callback for dateOverrides may return undefined on some paths; change the
logic so the predicate always returns a boolean and use a clearer API (e.g.,
replace dateOverrides.find(...) with dateOverrides.some(...)) and return
explicit true/false from the callback. Locate the block referencing
dateOverrides.find, organizerTimeZone, slotStartTime, slotEndTime and
dateOverrideExist; set dateOverrideExist when a matching date is found and have
the callback return true for both "slot outside override" and "slot
equals/overlaps" cases and false otherwise (or use some() to directly assign
dateOverrideExist = dateOverrides.some(...)). Ensure no path leaves the callback
without an explicit boolean return.

Comment on lines +114 to +116
if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) {
return true;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bug: Dayjs object comparison with === always returns false.

Comparing two Dayjs objects with === checks reference equality, not value equality. This condition will never be true, even when the start and end times are identical.

🐛 Proposed fix using `.isSame()`
-        if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) {
+        if (dayjs(date.start).add(utcOffset, "minutes").isSame(dayjs(date.end).add(utcOffset, "minutes"))) {
           return true;
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (dayjs(date.start).add(utcOffset, "minutes") === dayjs(date.end).add(utcOffset, "minutes")) {
return true;
}
if (dayjs(date.start).add(utcOffset, "minutes").isSame(dayjs(date.end).add(utcOffset, "minutes"))) {
return true;
}
🤖 Prompt for AI Agents
In `@packages/trpc/server/routers/viewer/slots.ts` around lines 114 - 116, The
comparison using === between two Dayjs objects (dayjs(date.start).add(utcOffset,
"minutes") === dayjs(date.end).add(utcOffset, "minutes")) is wrong; replace it
with Dayjs value equality using .isSame(), e.g. call .isSame(...) on one of the
adjusted Dayjs instances (optionally with a granularity like "minute") so the
condition correctly detects identical moments; update the expression where this
check occurs to use .isSame instead of ===.

Comment on lines +138 to +151
if (
workingHours.find((workingHour) => {
if (workingHour.days.includes(slotStartTime.day())) {
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
const end = slotStartTime.hour() * 60 + slotStartTime.minute();
if (start < workingHour.startTime || end > workingHour.endTime) {
return true;
}
}
})
) {
// slot is outside of working hours
return false;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Refactor: Working hours find() callback should explicitly return a boolean.

Same issue as above - this callback only returns true when the slot is outside working hours, but implicitly returns undefined otherwise.

♻️ Suggested refactor
-  if (
-    workingHours.find((workingHour) => {
-      if (workingHour.days.includes(slotStartTime.day())) {
-        const start = slotStartTime.hour() * 60 + slotStartTime.minute();
-        const end = slotEndTime.hour() * 60 + slotEndTime.minute();
-        if (start < workingHour.startTime || end > workingHour.endTime) {
-          return true;
-        }
-      }
-    })
-  ) {
+  const slotOutsideWorkingHours = workingHours.some((workingHour) => {
+    if (!workingHour.days.includes(slotStartTime.day())) {
+      return false;
+    }
+    const start = slotStartTime.hour() * 60 + slotStartTime.minute();
+    const end = slotEndTime.hour() * 60 + slotEndTime.minute();
+    return start < workingHour.startTime || end > workingHour.endTime;
+  });
+
+  if (slotOutsideWorkingHours) {
     // slot is outside of working hours
     return false;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
workingHours.find((workingHour) => {
if (workingHour.days.includes(slotStartTime.day())) {
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
const end = slotStartTime.hour() * 60 + slotStartTime.minute();
if (start < workingHour.startTime || end > workingHour.endTime) {
return true;
}
}
})
) {
// slot is outside of working hours
return false;
}
const slotOutsideWorkingHours = workingHours.some((workingHour) => {
if (!workingHour.days.includes(slotStartTime.day())) {
return false;
}
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
const end = slotEndTime.hour() * 60 + slotEndTime.minute();
return start < workingHour.startTime || end > workingHour.endTime;
});
if (slotOutsideWorkingHours) {
// slot is outside of working hours
return false;
}
🧰 Tools
🪛 Biome (2.3.13)

[error] 139-139: This callback passed to find() iterable method should always return a value.

Add missing return statements so that this callback returns a value on all execution paths.

(lint/suspicious/useIterableCallbackReturn)

🤖 Prompt for AI Agents
In `@packages/trpc/server/routers/viewer/slots.ts` around lines 138 - 151, The
callback passed to workingHours.find currently only returns true when a
violation is detected and otherwise returns undefined; update the callback for
workingHours.find (the anonymous arrow function referencing workingHour and
slotStartTime) to always return a boolean — return true when the slot is outside
the workingHour bounds and explicitly return false otherwise (and also ensure
you compute start/end from the correct slot time variables if needed) so find()
behaves predictably.

Comment on lines +141 to +145
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
const end = slotStartTime.hour() * 60 + slotStartTime.minute();
if (start < workingHour.startTime || end > workingHour.endTime) {
return true;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Both start and end are computed from slotStartTime, causing incorrect working hours validation.

Line 142 computes end identically to start, using slotStartTime for both. This means the working hours boundary check on line 143 is comparing start < workingHour.startTime || start > workingHour.endTime which doesn't account for the slot's duration.

🐛 Proposed fix
       if (workingHour.days.includes(slotStartTime.day())) {
         const start = slotStartTime.hour() * 60 + slotStartTime.minute();
-        const end = slotStartTime.hour() * 60 + slotStartTime.minute();
+        const end = slotEndTime.hour() * 60 + slotEndTime.minute();
         if (start < workingHour.startTime || end > workingHour.endTime) {
           return true;
         }
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
const end = slotStartTime.hour() * 60 + slotStartTime.minute();
if (start < workingHour.startTime || end > workingHour.endTime) {
return true;
}
const start = slotStartTime.hour() * 60 + slotStartTime.minute();
const end = slotEndTime.hour() * 60 + slotEndTime.minute();
if (start < workingHour.startTime || end > workingHour.endTime) {
return true;
}
🤖 Prompt for AI Agents
In `@packages/trpc/server/routers/viewer/slots.ts` around lines 141 - 145, The
working-hours check incorrectly computes both start and end from slotStartTime;
update the end calculation to use the slot's end time (e.g., slotEndTime.hour()
* 60 + slotEndTime.minute() or slotStartTime.plus(duration) if slotEndTime
variable is named differently) so that the start/end values accurately reflect
the slot span, then keep the existing boundary check against
workingHour.startTime and workingHour.endTime using the corrected end variable;
adjust any variable names in the block containing start, end, slotStartTime and
workingHour accordingly.

Sign in to join this conversation on GitHub.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant